using System;
using System.Collections.Generic;
using System.Text;
using Chan_cli.Modules;
using Chanutils;

namespace Chan_cli
{

    // Wrapping HTTPSinglePair[] to something that makes more sense.
    public struct POSTData
    {
        public string Name;
        public string Value;
    }

    public class SrvHooks
    {
    	#region Declarations
    	
	    	// Background thread declarations...
	        System.Threading.Thread bkThread;
	        bool bThreadCanRun = true;
	        
	        // HTTP server module declarations...
	        HTTPSrv.HTTPSrv hSrv;
	        
	        // Dictionary of loaded modules
	        Dictionary<string, Module> Modules;
	        
	    #endregion

        #region Initialization & Destruction
	        /// <summary>
	        /// Creates a new instance of SrvHooks, and starts the background thread.
	        /// </summary>
	        /// <param name="IP">IP address to make the HTTP server listen on</param>
	        /// <param name="Port">Port number to make the HTTP server listen on</param>
	        public SrvHooks(string IP, int Port)
	        {
	
	            // start listening...
	            hSrv = new HTTPSrv.HTTPSrv(System.Net.IPAddress.Parse(IP), Port);
	
	            // Create empty dictionary of modules
	            Modules = new Dictionary<string, Module>();
	
	            // start background polling thread
	            bkThread = new System.Threading.Thread(new System.Threading.ThreadStart(BackgroundOp));
	            bkThread.Start();
	
	            // Install standard hooks.
	            hSrv.OnPathExistCheck += new HTTPSrv.HTTPOnPathExistCheck(DoesPathExist);
	            hSrv.OnRequest += new HTTPSrv.HTTPOnRequest(GETRequest);
	            hSrv.OnRequestWithData += new HTTPSrv.HTTPOnRequestWithData(POSTRequest);
	        }
	        
	        ~SrvHooks()
	        {
	        	// Just in case...
	        	KillBackgroundThread();
	        	
	        	// Kill other shite.
	            bkThread = null;
                hSrv = null;
                Modules = null;

	        }
        #endregion

        #region Module management (adding, deletion (TODO), and so on...)
	        /// <summary>
	        /// Adds a module hook to the server so that when the specified url-prefix is requested, control is passed to the correct module
	        /// </summary>
	        /// <param name="Name">The prefix of the url that triggers the module's calling. eg. A value of "test" will trigger for both "/test/" and "/test/params/1/2/3".</param>
	        /// <param name="Mod">A Module structure containing references to methods for handling different HTTP requests.</param>
            public void AddModule(string Name, Module Mod)
	        {
	
	            // Exist?
	            if (Modules.ContainsKey(Name))
	                // Update? Hm.
	                Modules[Name] = Mod;
	            else
	                Modules.Add(Name, Mod);
	       
	        }
	
	        // You should probably also add a DeleteModule(), but I don't know if you'd like it to
	        // throw an exception, error, or what. :P You know me, I'd just have it silently die.
	        
	        public void NotifyDirectoryUpdate()
	        {
	            // This notifies (or _will_ notify..) any modules that belong to us,
	            //  that either the /archive, /cache or /files directory location have
	            //  been modified.
	            //
	            // We should pass this on, and we would, if we actually _had_ any modules...
	            
	            // TODO NotifyDirectoryUpdate()
	        }
	    #endregion

        #region HTTPSrv Hook Implementations
	        private bool DoesPathExist(string Path)
	        {
	            try
	            {
		            // Split path
		            string[] sPath = SplitPath(Path);
		
		            // Does a module exist for it?
		            if (Modules.ContainsKey(sPath[0]))
		                // Pass to module to check
		                return Modules[sPath[0]].PathExist(sPath);
		            else
		                // Well if the module doesn't exist, the path sure wont.
		                return false;
	            }
	            catch (Exception e)
	            {
	                // Dodgy..
	                Chanutils.HELP.DumpException(e);
	                return false;
	            }
	        }
	
	        private byte[] GETRequest(string Path)
	        {
	            try
	            {
	                // Split path by / ...
		            string[] sPath = SplitPath(Path);
		
		            // We know the module exists, because HTTPSrv calls DoesPathExist() :)
		
		            // Call the module, and return the results :)
		            return Modules[sPath[0]].Request(sPath);
	            }
	            catch (Exception e)
	            {
	                return DealWithRequestException(e);
	            }
	        }
	        
	        private byte[] POSTRequest(string Path, HTTPSrv.HTTPSinglePair[] Data)
	        {
	            try
	            {
                    // Split path by / ...
                    string[] sPath = SplitPath(Path);

                    // LoL
                    // We _know_ Modules[sPath[0]] exists, due to DoesPathExist() being exec'd always before us...

                    // Call moddy and return ze resultehhzadlcxkfg;sadf;aglksz..th.
                    // .. Unless it isn't hooked, in which case return debugging crap instead...
                    return Modules[sPath[0]].RequestData(sPath, ConvertManyPairsToManyPOSTData(Data));
	            
	            }
	            catch (Exception e)
	            {
	                return DealWithRequestException(e);
	            }
	        }
		#endregion
        
        #region Utility functions 
	        /// <summary>
	        /// Used by the 'translucent' hook functions to deal with exceptions without cutting short
	        /// the HTTP connection, and causing hanging/lost mem etcetcetc.
	        /// </summary>
	        /// <param name="ex">The exception to turn into an appropriate return format</param>
	        /// <returns>Something you should return from one of the translucent hook functions, when you need to..</returns>
	        private byte[] DealWithRequestException(Exception ex)
	        {
	            // Debug info...
	            Chanutils.HELP.DumpException(false, ex);
	
	            // Mmm, HTTPishness.
	            HTTPSrv.HTTPUtil.HTTPStatusCodes hsc = HTTPSrv.HTTPUtil.GetHTTPCodeRepresentationOfException(ex);
	
	            return System.Text.Encoding.ASCII.GetBytes(XHTMLPoink.XHTMLErrorGen.GenerateErrorPage((int)hsc, HTTPSrv.HTTPUtil.GetHumanReadableDescriptionOfHTTPCode(hsc), HTTPSrv.HTTPUtil.GetProperDescOfHTTPCode(hsc)));
	        }
	
	
	        private byte[] POSTTesting(string Path, HTTPSrv.HTTPSinglePair[] Data)
	        {
	            // Yes.
	            // Bwahahaa(!)
	            // Idioths. :(
	            XHTMLPoink.XHTMLDocument xhd = new XHTMLPoink.XHTMLDocument("POST Debug");
	
	            xhd.AddHeading(1, "POST Debug");
	
	            xhd.AddHeading(3, "Filename requested (not a chance, TODO etcs.): '" + Path + "'");
	
	            foreach (HTTPSrv.HTTPSinglePair hsp in Data)
	            {
	                xhd.AddText("HTTPSinglePair.HeaderName: " + hsp.HeaderName);
	                xhd.AddLineBreak();
	                xhd.AddText("HTTPSinglePair.HeaderValue: " + hsp.HeaderValue);
	                xhd.AddLineBreak();
	            }
	
	            return System.Text.Encoding.ASCII.GetBytes(xhd.GimmeTehCodez());
	        }
	
            /// <summary>
            /// This splits a path from a single string, "/test/1/2/3" to an array of "{test,1,2,3}".
            /// </summary>
            /// <param name="Path">A string representation (with leading slash) of a path, to be split into a string array.</param>
            /// <returns>A string array representation of the path, where the 0th value is the module being called..</returns>
	        private string[] SplitPath(string Path)
	        {
	
	            // We'll place this in a separate function, as loads of crap will need it >.> 
	            // Makes sense, right?
	
	            // Split by '/', but we don't want the first '/' there, otherwise sPath[0] = ""!
	            string[] sPath = Path.TrimStart("/".ToCharArray()).Split("/".ToCharArray());
	
	            // Go through and do .ToLower() on 'em all, these urls shouldn't be case sensitive,
	            // correct me if I'm wrong.
	            for (int i = 0; i < sPath.Length; i++)
	                sPath[i] = sPath[i].ToLower();
	
	            // Return the new array
	            return sPath;
	
	        }

            // .. Shh.
            private POSTData ConvertPairsToPOSTData(HTTPSrv.HTTPSinglePair Pair)
            {
                POSTData pstData = new POSTData();

                pstData.Name = Pair.HeaderName;
                pstData.Value = Pair.HeaderValue;

                return pstData;
            }

            private POSTData[] ConvertManyPairsToManyPOSTData(HTTPSrv.HTTPSinglePair[] Pairs)
            {
                POSTData[] pstDatas = new POSTData[Pairs.Length];

                for (int i = 0; i <= Pairs.Length - 1; i++)
                    pstDatas[i] = ConvertPairsToPOSTData(Pairs[i]);

                return pstDatas;
            }
        #endregion

        #region Background thread-related stuffs (Main loop, shut-down proc etc.)
            /// <summary>
            /// Background thread, main loop.
            /// </summary>
            private void BackgroundOp()
	        {
	            do
	            {
	                try
	                {
	                    hSrv.Poll();
	                    System.Threading.Thread.Sleep(10);
	                }
	                catch (Exception e)
	                {
	                    HELP.DumpException(e);
	                }
	            }
	            while (bThreadCanRun);  // I'M VERY LAZY TODAY
	        }
	        
	        /// <summary>
	        /// Signals that the background thread should stop processing, and shut down.
	        /// </summary>
	        public void KillBackgroundThread()
	        {
	            bThreadCanRun = false;
	            System.Threading.Thread.Sleep(30);
	        }
		#endregion

    }
}
